/*  
 *  Copyright (c) 2008 Adrian Necimal <necimal@gmail.com>
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

const Cc = Components.classes;
const Ci = Components.interfaces;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

/*
 * Communication with the Last.fm client
 */

var LastfmClient = {
  load: function() {
    // read some settings
    var prefsService = Cc['@mozilla.org/preferences-service;1']
      .getService(Ci.nsIPrefBranch);
      
    this._autolaunch = prefsService.getBoolPref("extensions.songbird-scrobbler.autolaunch");
    this._customexe = prefsService.getCharPref("extensions.songbird-scrobbler.customexe");
    this._debug = prefsService.getBoolPref("extensions.songbird-scrobbler.debug");
    this._host = prefsService.getCharPref("extensions.songbird-scrobbler.host");
    this._port = prefsService.getIntPref("extensions.songbird-scrobbler.port");
    
    this._transportService = Components.classes["@mozilla.org/network/socket-transport-service;1"]
                               .getService(Components.interfaces.nsISocketTransportService);
    
    this._console = Components.classes["@mozilla.org/consoleservice;1"]
                      .getService(Components.interfaces.nsIConsoleService);
    
    this._cmdthread = Components.classes["@mozilla.org/thread-manager;1"]
                        .getService().newThread(0);
    this._nextCmdId = 0;
    
    this.logMessage("debugging on");
    
    if (this._autolaunch) {
      if (this.launchClient()) {
        this.logMessage("successfully launched the last.fm client");
      }
      else
      {
        this.logMessage("couldn't launch the last.fm client!");
      }
    }
    
    this.logMessage("host " + this._host + ":" + this._port);
  },
  
  postCommand: function(cmd) {
    // get an id
    id = this._nextCmdId;
    this._nextCmdId++;
    
    // process the command asyncronously
    var cmdInfo = new this.CommandInfo(cmd, id);
    this._cmdthread.dispatch(cmdInfo, 0);
  },
  
  logMessage: function(msg) {
    if (this._debug) {
      this._console.logStringMessage("songbird-scrobbler: " + msg);
    }
  },
  
  escapeString: function(cmd) {
    if (!cmd) return "";
    else return cmd.replace(/&/ig, "&&");
  },
  
  CommandInfo: function(cmd, id) {
    this._cmd = cmd;
    this._id = id;
  },
  
  launchClient: function() {
    // try user-specified exe path
    if (this.startExe(this._customexe)) {
      return true;
    }
    
    // try getting path from windows registry
    var key = "Software\\Last.fm\\Client";
    
    try {
      var reg = Components.classes["@mozilla.org/windows-registry-key;1"]
                  .createInstance(Components.interfaces.nsIWindowsRegKey);
      reg.open(reg.ROOT_KEY_CURRENT_USER, key, reg.ACCESS_READ);
      command = reg.readStringValue("Path");
      if (this.startExe(command)) {
        return true;
      }
      reg.close();
    } catch (ex) { }
    
    try {
      var reg = Components.classes["@mozilla.org/windows-registry-key;1"]
                  .createInstance(Components.interfaces.nsIWindowsRegKey);
      reg.open(reg.ROOT_KEY_LOCAL_MACHINE, key, reg.ACCESS_READ);
      command = reg.readStringValue("Path");
      if (this.startExe(command)) {
        return true;
      }
      reg.close();
    } catch (ex) { }
    
    // try some hard-coded defaults
    var defaults = ["C:\\Program Files\\Last.fm\\LastFM.exe",
                    "/Applications/Last.fm.app/Contents/MacOS/Last.fm",
                    "/usr/bin/lastfm",
                    "/usr/bin/last.fm",
                    "/usr/local/bin/lastfm",
                    "/usr/local/bin/last.fm"];
    for (var d in defaults) {
      if (this.startExe(defaults[d])) {
        return true;
      }
    }
    
    // give up
    return false;
  },
  
  startExe: function(path) {
    // try to start the Last.fm client from the specified path
    var args = ["--tray"];
    
    try {
      var file = Components.classes["@mozilla.org/file/local;1"]
                   .createInstance(Components.interfaces.nsILocalFile);
      file.initWithPath(path);
      
      if (file.exists && file.isExecutable) {
        var process = Components.classes["@mozilla.org/process/util;1"]
                        .createInstance(Components.interfaces.nsIProcess);
        process.init(file);
        process.run(false, args, args.length);
        return true;
      } else {
        return false;
      }
    } catch (ex) {
      return false;
    }
  }
}

LastfmClient.CommandInfo.prototype = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRunnable]),
  
  run: function() {
    LastfmClient.logMessage("send (" + this._id + "): " + this._cmd);
    
    // get the transport
    var transport = LastfmClient._transportService.createTransport(null, 0, LastfmClient._host, LastfmClient._port, null);
    
    // open streams
    var instream = transport.openInputStream(transport.OPEN_BLOCKING, 0, 0);
    var outstream = transport.openOutputStream(transport.OPEN_BLOCKING, 0, 0);
    
    // convert to UTF8
    var utf8outstream = Cc["@mozilla.org/intl/converter-output-stream;1"]
                          .createInstance(Ci.nsIConverterOutputStream);
    utf8outstream.init(outstream, null, 0, '?');
    
    // write to the stream
    utf8outstream.writeString(this._cmd);
    utf8outstream.close();
    
    // read from the stream
    var sinstream = Cc["@mozilla.org/scriptableinputstream;1"]
                      .createInstance(Ci.nsIScriptableInputStream);
    sinstream.init(instream);

    try {
      var rcvd = "";
      do {
        rcvd += sinstream.read(50);
      } while (rcvd.length < 1);
      
      LastfmClient.logMessage("rcvd (" + this._id + "): " + rcvd);
    }
    catch (ex) {
      LastfmClient.logMessage("error (" + this._id + "): " + ex.name);
    }
    finally {
      sinstream.close();
    }
  }
}

/*
 * The sbScrobbler component
 */

function SongbirdScrobbler() {
  this.wrappedJSObject = this;
};

SongbirdScrobbler.prototype = {
  AS_PLUGIN_ID: "sbd",
  
  classDescription: "Songbird Audioscrobbler Javascript XPCOM Component",
  classID: Components.ID("{0D038D3B-F087-4827-9A84-55DB7CE9F0DA}"),
  contractID: "@necimal.com/scrobbler;1",
  
  QueryInterface: XPCOMUtils.generateQI([]),
  
  load: function() {
    // initialise the LastfmClient class
    LastfmClient.load();
  },
  
  start: function(artist, track, album, length, url) {
    LastfmClient.postCommand("START c=" + this.AS_PLUGIN_ID +
                             "&a=" + LastfmClient.escapeString(artist) +
                             "&t=" + LastfmClient.escapeString(track) +
                             "&b=" + LastfmClient.escapeString(album) +
                             "&l=" + length +
                             "&p=" + LastfmClient.escapeString(url) + "\n");
  },
  
  stop: function() {
    LastfmClient.postCommand("STOP c=" + this.AS_PLUGIN_ID + "\n");
  },
  
  pause: function() {
    LastfmClient.postCommand("PAUSE c=" + this.AS_PLUGIN_ID + "\n");
  },
  
  resume: function() {
    LastfmClient.postCommand("RESUME c=" + this.AS_PLUGIN_ID + "\n");
  }
}

var components = [SongbirdScrobbler];
function NSGetModule(compMgr, fileSpec) {
  return XPCOMUtils.generateModule(components);
}
